#include "CLogicSocket.h"
#include "CMemory.h"
#include "CLock.h"
#include "CCRC32.h"
#include "CLogger.h"

#include <arpa/inet.h>

//定义成员函数指针
typedef bool (CLogicSocket::* handler)(lp_connection_t pConn,	//连接池中连接的指针
										LPMsgHeader pMsgHeader,		//消息头指针
										char* pPkgBody,				//包体指针
										unsigned short iBodyLen		//包体长度
										);

//用来保存成员函数指针的数组
static const handler szMsgHandler[] = {
	//数组前5个元素保留，以备将来增加一些基本服务器功能
	&CLogicSocket::handlePing,					//【0】：心跳包实现
	NULL,
	NULL,
	NULL,
	NULL,
	&CLogicSocket::handleRegister,				//【5】：实现具体的注册功能
	&CLogicSocket::handleLogin					//【6】：实现具体的登录功能
};

#define TOTAL_HANDLERS sizeof(szMsgHandler) / sizeof(handler)	//有多少个消息处理函数，编译时即可知道

CLogicSocket::CLogicSocket()
{

}

CLogicSocket::~CLogicSocket()
{
}

//初始化函数【fork()子进程之前干这个事情】
//成功返回true，失败返回false
//直接调用父类的同名函数
bool CLogicSocket::init()
{
	return CSocket::init();
}

//发送没有包体的数据包给客户端
void CLogicSocket::sendNoBodyPkgToClient(LPMsgHeader pMsgHeader, unsigned short iMsgCode)
{
	CMemory* pMemory = CMemory::getInstance();
	//消息头 + 包头
	char* pSendBuf = (char*)pMemory->allocMemory(m_iMsgHeaderLen + m_iPkgHeaderLen, false);
	char* pTmpBuf = pSendBuf;

	memcpy(pTmpBuf, pMsgHeader, m_iMsgHeaderLen);
	pTmpBuf += m_iMsgHeaderLen;

	LPPkgHeader pPkgHeader = (LPPkgHeader)pTmpBuf;
	pPkgHeader->msgCode = htons(iMsgCode);
	pPkgHeader->pkgLen = htons(m_iPkgHeaderLen);
	pPkgHeader->crc32 = 0;
	msgSend(pSendBuf);
}

//处理注册的业务逻辑
bool CLogicSocket::handleRegister(lp_connection_t pConn, LPMsgHeader pMsgHeader, char* pPkgBody, unsigned short iBodyLen)
{
	//1.判断包体的合法性
	if (pPkgBody == NULL)	//具体看客户端和服务器的约定，如果约定这个命令【msgCode】必须带包体，如果没有包体，就是恶意包，直接不处理
	{
		return false;
	}

	int iRecvLen = sizeof(MsgRegister);
	if (iRecvLen != iBodyLen)	//发送过来的结构大小不对，认为是恶意包，直接不处理
	{
		return false;
	}

	//2.对于同一个用户，可能同时发送过来多个请求，造成多个线程同时为该用户服务
	//比如以网游为例，用户要在商店买A物品，又买B物品，而用户的钱，只够买A或者B，不够同时买A和B
	//如果用户发送购买命令买了一次A，又买了一次B，如果是两个线程来执行同一个用户的这两次不同的购买命令，
	//很可能造成这个用户成功购买了A和B
	//所以为了稳妥起见，针对某个用户的命令，我们一般互斥，我们需要增加临界的变量放在connection_t中
	CLock lock(&pConn->stLogicMutex);	//凡是和本用户有关的访问都互斥

	//3.取得了整个发送过来的数据
	LPMsgRegister pRecvInfo = (LPMsgRegister)pPkgBody;
	pRecvInfo->iType = ntohl(pRecvInfo->iType);	//所有数值型，short，int，long，uint64_t，int64_t这种都要传输前主机转网络序，收到后网络转主机序
	pRecvInfo->szUsername[sizeof(pRecvInfo->szUsername) - 1] = 0;	//防止客户端发送过来畸形包，导致整个服务器直接使用这个数据出现错误
	pRecvInfo->szPassword[sizeof(pRecvInfo->szPassword) - 1] = 0;   //防止客户端发送过来畸形包，导致整个服务器直接使用这个数据出现错误
	
	//4.这里可能要考虑业务逻辑，进一步判断数据的合法性

	//5.给客户端返回数据时，一般也是返回一个结构，这个机构具体由客户端/服务器协商，这里给客户端返回同样的MsgRegister
	LPPkgHeader pPkgHeader;
	CMemory* pMemory = CMemory::getInstance();
	CCRC32* pCRC32 = CCRC32::getInstance();
	int iSendLen = sizeof(MsgRegister);

	//6.分配要发送出去的包的内存
	char* pSendBuf = (char*)pMemory->allocMemory(m_iMsgHeaderLen + m_iPkgHeaderLen + iSendLen, false);	//准备发送的格式，消息头+包头+包体

	//7.填充消息头，直接拷贝进来
	memcpy(pSendBuf, pMsgHeader, m_iMsgHeaderLen);

	//8.填充包头
	pPkgHeader = (LPPkgHeader)(pSendBuf + m_iMsgHeaderLen);	//指向包头
	pPkgHeader->msgCode = CMD_REGISTER;						//消息代码
	pPkgHeader->msgCode = htons(pPkgHeader->msgCode);		//htons主机序转网络序
	pPkgHeader->pkgLen = htons(m_iMsgHeaderLen + iSendLen);	//整个包的尺寸【包头+包体】

	//9.填充包体，跳过消息头，跳过包头，就是包体了
	LPMsgRegister pSendInfo = (LPMsgRegister)(pSendBuf + m_iMsgHeaderLen + m_iPkgHeaderLen);
    //根据需要，填充要发回给客户端的内容
    pSendInfo->iType = 200;
    pSendInfo->iType = htonl(pSendInfo->iType);
//    strcpy(pSendInfo->szUsername, pRecvInfo->szUsername);
//    strcpy(pSendInfo->szPassword, pRecvInfo->szPassword);
    memcpy(pSendInfo->szUsername, pRecvInfo->szUsername, sizeof(pSendInfo->szUsername));
    memcpy(pSendInfo->szPassword, pRecvInfo->szPassword, sizeof(pSendInfo->szPassword));

	//10.包体内容全部确定好后，计算包体的crc32值
	pPkgHeader->crc32 = pCRC32->getCRC((unsigned char*)pSendInfo, iSendLen);
	pPkgHeader->crc32 = htonl(pPkgHeader->crc32);

	//11.发送数据包，只是将数据包放到队列中，有相应的线程专门发送
	msgSend(pSendBuf);

	return true;
}

bool CLogicSocket::handleLogin(lp_connection_t pConn, LPMsgHeader pMsgHeader, char* pPkgBody, unsigned short iBodyLen)
{
	if (pPkgBody == NULL)
	{
		return false;
	}

	int iRecvLen = sizeof(MsgLogin);
	if (iRecvLen != iBodyLen)
	{
		return false;
	}

	CLock lock(&pConn->stLogicMutex);
	LPMsgLogin pRecvInfo = (LPMsgLogin)pPkgBody;
	pRecvInfo->szUsername[sizeof(pRecvInfo->szUsername) - 1] = 0;	//防止客户端发送过来畸形包，导致整个服务器直接使用这个数据出现错误
	pRecvInfo->szPassword[sizeof(pRecvInfo->szPassword) - 1] = 0;   //防止客户端发送过来畸形包，导致整个服务器直接使用这个数据出现错误

	//4.这里可能要考虑业务逻辑，进一步判断数据的合法性

	//5.给客户端返回数据时，一般也是返回一个结构，这个机构具体由客户端/服务器协商，这里给客户端返回同样的MsgRegister
	LPPkgHeader pPkgHeader;
	CMemory* pMemory = CMemory::getInstance();
	CCRC32* pCRC32 = CCRC32::getInstance();
	int iSendLen = sizeof(MsgLogin);

	//6.分配要发送出去的包的内存
	char* pSendBuf = (char*)pMemory->allocMemory(m_iMsgHeaderLen + m_iPkgHeaderLen + iSendLen, false);	//准备发送的格式，消息头+包头+包体

	//7.填充消息头，直接拷贝进来
	memcpy(pSendBuf, pMsgHeader, m_iMsgHeaderLen);

	//8.填充包头
	pPkgHeader = (LPPkgHeader)(pSendBuf + m_iMsgHeaderLen);	//指向包头
	pPkgHeader->msgCode = CMD_LOGIN;						//消息代码
	pPkgHeader->msgCode = htons(pPkgHeader->msgCode);		//htons主机序转网络序
	pPkgHeader->pkgLen = htons(m_iMsgHeaderLen + iSendLen);	//整个包的尺寸【包头+包体】

	//9.填充包体，跳过消息头，跳过包头，就是包体了
	LPMsgLogin pSendInfo = (LPMsgLogin)(pSendBuf + m_iMsgHeaderLen + m_iPkgHeaderLen);
	//根据需要，填充要发回给客户端的内容
	memcpy(pSendInfo->szUsername, pRecvInfo->szUsername, sizeof(pSendInfo->szUsername));
	memcpy(pSendInfo->szPassword, pRecvInfo->szPassword, sizeof(pSendInfo->szPassword));

	//10.包体内容全部确定好后，计算包体的crc32值
	pPkgHeader->crc32 = pCRC32->getCRC((unsigned char*)pSendInfo, iSendLen);
	pPkgHeader->crc32 = htonl(pPkgHeader->crc32);

	//11.发送数据包，只是将数据包放到队列中，有相应的线程专门发送
	msgSend(pSendBuf);

	return true;
}

bool CLogicSocket::handlePing(lp_connection_t pConn, LPMsgHeader pMsgHeader, char* pPkgBody, unsigned short iBodyLen)
{
	//心跳包要求没有包体
	if (iBodyLen != 0)	//有包体则被认为是非法包
	{
		return false;
	}

	CLock lock(&pConn->stLogicMutex);
	pConn->lLastPingTime = time(NULL);

	//服务器也发送一个只有包头的数据包给客户端，作为返回的数据
	sendNoBodyPkgToClient(pMsgHeader, CMD_PING);

	return true;
}

//心跳包检测时间到，该去检测心跳包是否超时，本函数是子类函数，实现具体的判断动作
void CLogicSocket::procPingTimeout(LPMsgHeader pTmpMsg, time_t llCurTime)
{
	CMemory* pMemory = CMemory::getInstance();
	if (pTmpMsg->uCurrSequence == pTmpMsg->pConn->iCurrSequence)	//此连接没断
	{
		lp_connection_t pConn = pTmpMsg->pConn;
		if (m_bTimeoutKick)	
		{
			afterSocketClose(pConn);
		}
		else if (llCurTime - pConn->lLastPingTime > m_iWaitTime * 3 + 10) {
			//超时踢人标准：每次检查的时间间隔*3，超过这个时间没发心跳包，就踢出去
			afterSocketClose(pConn);
		}
		pMemory->freeMemory(pTmpMsg);
	}
	else {	//连接断了
		pMemory->freeMemory(pTmpMsg);
	}
}

//处理收到的数据包，由线程池来调用本函数，本函数是一个单独的线程
//pMsgBuf: 消息头 + 包头 + 包体
void CLogicSocket::threadRecvProc(char* pMsgBuf)
{
	LPMsgHeader pMsgHeader = (LPMsgHeader)pMsgBuf;
	LPPkgHeader pPkgHeader = (LPPkgHeader)(pMsgBuf + m_iMsgHeaderLen);	//包头
	
	void* pPkgBody;											//指向包体的指针
	unsigned short uPkgLen = ntohs(pPkgHeader->pkgLen);		//客户端指明的包长度【包头+包体】

	if (m_iPkgHeaderLen == uPkgLen)	//没有包体，只有包头
	{
		if (pPkgHeader->crc32 != 0)	//只有包头，没有包体的情况下crc32值为0
		{
			return;
		}
		pPkgBody = NULL;
	}
	else {
		//有包体，走到这里
		pPkgHeader->crc32 = ntohl(pPkgHeader->crc32);		//针对4字节的数据，网络序转主机序
		pPkgBody = (void*)(pMsgBuf + m_iMsgHeaderLen + m_iPkgHeaderLen);	//跳过消息头，包头，指向包体

		//计算crc值判断包的完整性
		uint64_t iCrcResult = CCRC32::getInstance()->getCRC((unsigned char*)pPkgBody, uPkgLen - m_iPkgHeaderLen);	//计算纯包体的crc值
		if (iCrcResult != pPkgHeader->crc32)
		{
			CLogger::logStderr(0, "CLogicSocket::threadRecvProc()中CRC错误，服务器：%d/客户端：%d，丢弃数据！", iCrcResult, pPkgHeader->crc32);
			return;
		}
	}

	//包的CRC校验通过才能走到这里
	unsigned short iMsgCode = ntohs(pPkgHeader->msgCode);	//取出消息代码
	lp_connection_t pConn = pMsgHeader->pConn;				//取出连接指针

	//做一些判断
	//（1）如果从收到客户端发送来的包到服务器释放一个线程池中的线程处理该包的过程中，客户端断开了，那显然这种收到的包我们就不必处理了
	if (pConn->iCurrSequence != pMsgHeader->uCurrSequence)
	{
		return;
	}

	//（2）判断消息编号是不是正确的，防止客户端恶意侵害我们服务器，发送一个不在我们服务器处理范围的消息码
	if (iMsgCode >= TOTAL_HANDLERS)
	{
		CLogger::logStderr(0, "CLogicSocket::threadRecvProc()中iMsgCode=%d消息码不对！", iMsgCode);	//这种有恶意倾向或者错误倾向的包，打印出来
		return;
	}

	//(3)查找对应的消息出来函数
	if (szMsgHandler[iMsgCode] == NULL)
	{
		CLogger::logStderr(0, "CLogicSocket::threadRecvProc()中iMsgCode=%d消息码找不到对应的处理函数！", iMsgCode);	//这种有恶意倾向或者错误倾向的包，打印出来
		return;
	}

	//(4)调用消息处理函数来处理消息
	(this->*szMsgHandler[iMsgCode])(pConn, pMsgHeader, (char*)pPkgBody, uPkgLen - m_iPkgHeaderLen);
}
